#include <iostream>

#include <llvm/Support/raw_ostream.h>
#include <llvm/Support/CommandLine.h>

#include <clang/Basic/Diagnostic.h>
#include <clang/Basic/TargetInfo.h>
#include <clang/Basic/FileManager.h>
#include <clang/Basic/TargetOptions.h>
#include <clang/Basic/FileSystemOptions.h>
#include <clang/Lex/HeaderSearch.h>
#include <clang/Frontend/HeaderSearchOptions.h>
#include <clang/Lex/Preprocessor.h>
#include <clang/Frontend/TextDiagnosticPrinter.h>
#include <clang/Frontend/DiagnosticOptions.h>
#include <clang/FrontendTool/Utils.h>
#include <clang/Frontend/FrontendActions.h>
#include <clang/AST/ASTConsumer.h>
#include <clang/AST/DeclGroup.h>
#include <clang/AST/Decl.h>
#include <clang/AST/DeclTemplate.h>
#include <clang/AST/ASTContext.h>
#include <clang/Parse/ParseAST.h>

#include <HeadgenAstConsumer.h>
#include <HeadgenPPCallbacks.h>
#include <IdInfoContainer.h>

///// command line options.
static llvm::cl::list<std::string>
D_macros("D", llvm::cl::value_desc("macro"), llvm::cl::Prefix,
	llvm::cl::desc("Predefine the specified macro"));

static llvm::cl::list<std::string>
P_ids("P", llvm::cl::value_desc("identifier"), llvm::cl::Prefix,
	llvm::cl::desc("Define which identifiers are to be printed"));

static llvm::cl::list<std::string>
I_dirs("I", llvm::cl::value_desc("directory"), llvm::cl::Prefix,
	llvm::cl::desc("Add directory to include search path"));

static llvm::cl::opt<std::string>
InputFilename(llvm::cl::Positional, llvm::cl::desc("<input file>"),
	llvm::cl::Required);

static llvm::cl::list<std::string> IgnoredParams(llvm::cl::Sink);
static llvm::cl::opt<std::string> dummy("o", llvm::cl::desc("dummy for gcc compat"));



// Main.
int main(int argc, char * argv[]) {
	llvm::cl::ParseCommandLineOptions(argc, argv, " headgen version 0.0.1\n"
		  "  This generates C header file for given source file.");


	// Init clang.
	clang::TextDiagnosticPrinter *tdp =
			new clang::TextDiagnosticPrinter(llvm::outs(), clang::DiagnosticOptions());
	llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> diagIDs(new clang::DiagnosticIDs);
	clang::Diagnostic diag(diagIDs, tdp);

	clang::FileSystemOptions fsOptions;
	clang::LangOptions langOptions;
	clang::TargetOptions targetOptions;

	clang::FileManager fm(fsOptions);
	clang::HeaderSearch headerSearch(fm);


	// Add include paths.
	// TODO: Add system include dirs.
	// TODO: Add macro support.
	std::vector<clang::DirectoryLookup> dirs;
	for (int i = 0; i < I_dirs.size(); ++i) {
		dirs.push_back(
					clang::DirectoryLookup(fm.getDirectory(I_dirs[i]), clang::SrcMgr::C_User,
										   true, false));
	}
	headerSearch.SetSearchPaths(dirs, 0, false);

	clang::SourceManager sm(diag, fm);
	targetOptions.Triple = LLVM_HOSTTRIPLE;
	clang::TargetInfo * ti =
			clang::TargetInfo::CreateTargetInfo(diag, targetOptions);
	clang::Preprocessor	pp(diag, langOptions, *ti, sm, headerSearch);



	// Initialize identifier information container and printer object.
	IdInfoContainer idInfoContainer(&sm);

	// Add specific callbacks to Preprocessor.
	HeadgenPPCallbacks * ppc = new HeadgenPPCallbacks(idInfoContainer.m_headerMap);
	// FIXME lexer crashes if any error in input file is found.
	pp.addPPCallbacks(ppc);


	// Open input source file.
	const clang::FileEntry * file = fm.getFile(InputFilename);
	if (!file) {
		std::cout << "headgen: \"" << InputFilename << "\": file not found" << std::endl;
		return EXIT_FAILURE;
	}
	sm.createMainFileID(file);


	// Parse AST with specific ASTConsumer.
	clang::IdentifierTable idTable(langOptions);
	clang::SelectorTable selTable;
	clang::Builtin::Context builtins(*ti);
	clang::ASTContext astContext(langOptions, sm, *ti, idTable, selTable, builtins, 1024);

	HeadgenAstConsumer ac(sm, idInfoContainer.m_idMap, idInfoContainer.m_idList);
	clang::ParseAST(pp, &ac, astContext, false, true);


	// Mark which identifiers, their deps and includes are to be printed.
	idInfoContainer.markUsedIds(P_ids);


	// Write result to file.
	std::string ofile = InputFilename;
	ofile = ofile.substr(0, ofile.size() - 1) + "h";
	std::string errInfo;
	llvm::raw_fd_ostream os(ofile.c_str(), errInfo);
	idInfoContainer.print(os);
	os.flush();
	os.close();


	if (ti) {
		delete ti;
	}

	return 0;
}
